#include "sdk.h"
#include <wx/mdi.h>
#include <wx/listimpl.cpp>
#include "configuretoolsdlg.h"

template<> ToolsManager* Mgr<ToolsManager>::instance = nullptr;
template<> bool  Mgr<ToolsManager>::isShutdown = false;

WX_DEFINE_LIST( ToolsList );

const int idToolsConfigure = XRCID( "idToolsConfigure" );
const int idToolProcess = wxNewId();

BEGIN_EVENT_TABLE( ToolsManager, wxEvtHandler )
  EVT_MENU( idToolsConfigure, ToolsManager::OnConfigure )
  EVT_IDLE( ToolsManager::OnIdle )
  EVT_PIPEDPROCESS_STDOUT( idToolProcess, ToolsManager::OnToolStdOutput )
  EVT_PIPEDPROCESS_STDERR( idToolProcess, ToolsManager::OnToolErrOutput )
  EVT_PIPEDPROCESS_TERMINATED( idToolProcess, ToolsManager::OnToolTerminated )
END_EVENT_TABLE()

ToolsManager::ToolsManager()
  : m_Menu( nullptr ),
    m_pProcess( nullptr ),
    m_Pid( 0 ) {
  LoadTools();
  Manager::Get()->GetAppWindow()->PushEventHandler( this );
}

ToolsManager::~ToolsManager() {
  m_ItemsManager.Clear();
  m_Tools.DeleteContents( true );
  m_Tools.Clear();
}

void ToolsManager::CreateMenu( M_Code_unused wxMenuBar* menuBar ) {
}

void ToolsManager::ReleaseMenu( M_Code_unused wxMenuBar* menuBar ) {
}

bool ToolsManager::Execute( const cbTool* tool ) {
  if( m_pProcess ) {
    msging( _T( "另一个工具正在执行.\n"
                "请在启动另一个工具之前让它完成..." ),
            _T( "错误" ), wxICON_ERROR );
    return false;
  }
  if( !tool ) {
    return false;
  }
  wxString cmdline;
  wxString cmd = tool->GetCommand();
  wxString params = tool->GetParams();
  wxString dir = tool->GetWorkingDir();
  Manager::Get()->GetMacrosManager()->RecalcVars( nullptr, nullptr, nullptr );
  Manager::Get()->GetMacrosManager()->ReplaceMacros( cmd );
  Manager::Get()->GetMacrosManager()->ReplaceMacros( params );
  Manager::Get()->GetMacrosManager()->ReplaceMacros( dir );
  if( tool->GetLaunchOption() == cbTool::LAUNCH_NEW_CONSOLE_WINDOW ) {
    wxString term = Manager::Get()->GetConfigManager( _T( "app" ) )->Read( _T( "/console_terminal" ), DEFAULT_CONSOLE_TERM );
    term.Replace( _T( "$TITLE" ), _T( "'" ) + tool->GetName() + _T( "'" ) );
    cmdline << term << _T( " " );
    wxString baseDir = ConfigManager::GetExecutableFolder();
    if( wxFileExists( baseDir + _T( "/console_runner" ) ) ) {
      cmdline << baseDir << _T( "/console_runner" );
    }
  }
  if( !cmdline.Replace( _T( "$SCRIPT" ), cmd << _T( " " ) << params ) ) {
    cmdline << cmd;
  }
  if( !( Manager::Get()->GetMacrosManager() ) ) {
    return false;
  }
  wxSetWorkingDirectory( dir );
  dir = wxGetCwd();
  Manager::Get()->GetLogManager()->Log( F( _T( "Launching tool '%s': %s (in %s)" ), tool->GetName().c_str(), cmdline.c_str(), dir.c_str() ) );
  bool pipe = true;
  int flags = wxEXEC_ASYNC;
  switch( tool->GetLaunchOption() ) {
    case cbTool::LAUNCH_NEW_CONSOLE_WINDOW:
      pipe = false;
      break;
    case cbTool::LAUNCH_VISIBLE:
    case cbTool::LAUNCH_VISIBLE_DETACHED:
      flags |= wxEXEC_NOHIDE;
      pipe = false;
      break;
    case cbTool::LAUNCH_HIDDEN:
    default:
      break;
  }
  if( tool->GetLaunchOption() == cbTool::LAUNCH_VISIBLE_DETACHED ) {
    int pid = wxExecute( cmdline, flags );
    if( !pid ) {
      msging( _T( "无法执行工具。查看日志了解详细信息." ), _T( "错误" ), wxICON_ERROR );
      return false;
    } else {
      CodeBlocksLogEvent evtSwitch( cbEVT_SWITCH_TO_LOG_WINDOW, LogManager::app_log );
      Manager::Get()->ProcessEvent( evtSwitch );
    }
  } else {
    m_pProcess = new PipedProcess( &m_pProcess, this, idToolProcess, pipe, dir );
    m_Pid = wxExecute( cmdline, flags, m_pProcess );
    if( !m_Pid ) {
      msging( _T( "无法执行工具。查看日志了解详细信息." ), _T( "错误" ), wxICON_ERROR );
      delete m_pProcess;
      m_pProcess = nullptr;
      m_Pid = 0;
      return false;
    } else {
      CodeBlocksLogEvent evtSwitch( cbEVT_SWITCH_TO_LOG_WINDOW, LogManager::app_log );
      Manager::Get()->ProcessEvent( evtSwitch );
    }
  }
  return true;
}

void ToolsManager::AddTool( const cbTool* tool, bool save ) {
  if( tool ) {
    InsertTool( m_Tools.GetCount(), tool, save );
  }
}

void ToolsManager::InsertTool( int position, const cbTool* tool, bool save ) {
  m_Tools.Insert( position, new cbTool( *tool ) );
  if( save ) {
    SaveTools();
  }
}

void ToolsManager::RemoveToolByIndex( int index ) {
  int idx = 0;
  for( ToolsList::Node* node = m_Tools.GetFirst(); node; node = node->GetNext() ) {
    if( idx == index ) {
      DoRemoveTool( node );
      return;
    }
    ++idx;
  }
}

void ToolsManager::DoRemoveTool( ToolsList::Node* node ) {
  if( node ) {
    m_Tools.DeleteNode( node );
    SaveTools();
  }
}

cbTool* ToolsManager::GetToolByMenuId( int id ) {
  for( ToolsList::Node* node = m_Tools.GetFirst(); node; node = node->GetNext() ) {
    cbTool* tool = node->GetData();
    if( tool->GetMenuId() == id ) {
      return tool;
    }
  }
  return nullptr;
}

cbTool* ToolsManager::GetToolByIndex( int index ) {
  int idx = 0;
  for( ToolsList::Node* node = m_Tools.GetFirst(); node; node = node->GetNext() ) {
    cbTool* tool = node->GetData();
    if( idx == index ) {
      return tool;
    }
    ++idx;
  }
  return nullptr;
}

void ToolsManager::LoadTools() {
  ConfigManager* cfg = Manager::Get()->GetConfigManager( _T( "tools" ) );
  wxArrayString list = cfg->EnumerateSubPaths( _T( '/' ) );
  if( list.GetCount() ) {
    for( unsigned int i = 0; i < list.GetCount(); ++i ) {
      cbTool tool;
      tool.SetName( cfg->Read( _T( "/" ) + list[i] + _T( "/name" ) ) );
      if( tool.GetName().IsEmpty() ) {
        continue;
      }
      tool.SetCommand( cfg->Read( _T( "/" ) + list[i] + _T( "/command" ) ) );
      if( tool.GetCommand().IsEmpty() ) {
        continue;
      }
      tool.SetParams( cfg->Read( _T( "/" ) + list[i] + _T( "/params" ) ) );
      tool.SetWorkingDir( cfg->Read( _T( "/" ) + list[i] + _T( "/workingDir" ) ) );
      tool.SetLaunchOption( static_cast<cbTool::eLaunchOption>( cfg->ReadInt( _T( "/" ) + list[i] + _T( "/launchOption" ) ) ) );
      AddTool( &tool, false );
    }
    Manager::Get()->GetLogManager()->Log( F( _T( "配置了 %d 个工具" ), m_Tools.GetCount() ) );
  }
}

void ToolsManager::SaveTools() {
  ConfigManager* cfg = Manager::Get()->GetConfigManager( _T( "tools" ) );
  wxArrayString list = cfg->EnumerateSubPaths( _T( '/' ) );
  for( unsigned int i = 0; i < list.GetCount(); ++i ) {
    cfg->DeleteSubPath( list[i] );
  }
  int count = 0;
  for( ToolsList::Node* node = m_Tools.GetFirst(); node; node = node->GetNext() ) {
    cbTool* tool = node->GetData();
    wxString elem;
    wxString tmp;
    tmp.Printf( _T( "tool%2.2d" ), count++ );
    elem << _T( '/' ) << tmp  << _T( '/' );
    cfg->Write( elem + _T( "name" ), tool->GetName() );
    cfg->Write( elem + _T( "command" ), tool->GetCommand() );
    cfg->Write( elem + _T( "params" ), tool->GetParams() );
    cfg->Write( elem + _T( "workingDir" ), tool->GetWorkingDir() );
    cfg->Write( elem + _T( "launchOption" ), static_cast<int>( tool->GetLaunchOption() ) );
  }
}

void ToolsManager::BuildToolsMenu( wxMenu* menu ) {
  m_ItemsManager.Clear();
  m_Menu = menu;
  if( m_Menu->GetMenuItemCount() > 0 ) {
    m_ItemsManager.Add( menu, wxID_SEPARATOR, _T( "" ), _T( "" ) );
  }
  for( ToolsList::Node* node = m_Tools.GetFirst(); node; node = node->GetNext() ) {
    cbTool* tool = node->GetData();
    if( tool->GetName() == CB_TOOLS_SEPARATOR ) {
      m_ItemsManager.Add( menu, wxID_SEPARATOR, _T( "" ), _T( "" ) );
      continue;
    }
    if( tool->GetMenuId() == -1 ) {
      tool->SetMenuId( wxNewId() );
    }
    m_ItemsManager.Add( menu, tool->GetMenuId(), tool->GetName(), tool->GetName() );
    Connect( tool->GetMenuId(), -1, wxEVT_COMMAND_MENU_SELECTED,
             ( wxObjectEventFunction )( wxEventFunction )( wxCommandEventFunction ) &ToolsManager::OnToolClick );
  }
  if( m_Tools.GetCount() > 0 ) {
    m_ItemsManager.Add( menu, wxID_SEPARATOR, _T( "" ), _T( "" ) );
  }
}

int ToolsManager::Configure() {
  CodeBlocksEvent event( cbEVT_MENUBAR_CREATE_BEGIN );
  Manager::Get()->ProcessEvent( event );
  ConfigureToolsDlg dlg( Manager::Get()->GetAppWindow() );
  PlaceWindow( &dlg );
  dlg.ShowModal();
  SaveTools();
  BuildToolsMenu( m_Menu );
  CodeBlocksEvent event2( cbEVT_MENUBAR_CREATE_END );
  Manager::Get()->ProcessEvent( event2 );
  return 0;
}

void ToolsManager::OnConfigure( M_Code_unused wxCommandEvent& event ) {
  Configure();
}

void ToolsManager::OnToolClick( wxCommandEvent& event ) {
  cbTool* tool = GetToolByMenuId( event.GetId() );
  if( !Execute( tool ) ) {
    msging( _T( "无法执行 " ) + tool->GetName() );
  }
}

void ToolsManager::OnIdle( wxIdleEvent& event ) {
  if( m_pProcess ) {
    if( m_pProcess->HasInput() ) {
      event.RequestMore();
    }
  } else {
    event.Skip();
  }
}

void ToolsManager::OnToolStdOutput( CodeBlocksEvent& event ) {
  Manager::Get()->GetLogManager()->Log( _T( "stdout> " ) + event.GetString() );
}

void ToolsManager::OnToolErrOutput( CodeBlocksEvent& event ) {
  Manager::Get()->GetLogManager()->Log( _T( "stderr> " ) + event.GetString() );
}

void ToolsManager::OnToolTerminated( CodeBlocksEvent& event ) {
  m_Pid = 0;
  m_pProcess = nullptr;
  Manager::Get()->GetLogManager()->Log( F( _T( "工具执行已终止，状态为 %d" ), event.GetInt() ) );
}
